package com.qqt.csr.workbench.service;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Lists;
import com.qqt.csr.common.exception.StatusCode;
import com.qqt.csr.common.utils.CacheKeyUtil;
import com.qqt.csr.common.utils.JsonUtil;
import com.qqt.csr.common.utils.RedissonLockUtil;
import com.qqt.csr.common.utils.ServiceAssert;
import com.qqt.csr.workbench.entity.SkillSet;
import com.qqt.csr.workbench.mapper.SkillSetMapper;
import com.qqt.csr.workbench.vo.req.AddingSkillSetReqVO;
import com.qqt.csr.workbench.vo.req.MovingSkillSetReqVO;
import com.qqt.csr.workbench.vo.req.UpdatingSkillSetReqVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Service
public class SkillSetService extends ServiceImpl<SkillSetMapper, SkillSet> {
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private SkillSetMemberService skillSetMemberService;
    @Autowired
    private SkillSetConfigService skillSetConfigService;
    @Autowired
    private RedissonLockUtil redissonLockUtil;

    /**
     * 添加技能组
     *
     * @param req
     */
    public Long addSkillSet(AddingSkillSetReqVO req, String tenantId, Long creatorId) {
        SkillSet insertSs = new SkillSet();
        insertSs.setCreatorId(creatorId);
        insertSs.setName(req.getName());
        insertSs.setParentId(req.getParentId());
        insertSs.setTenantId(tenantId);

        if (!req.getParentId().equals(NumberUtils.LONG_ZERO)) {
            SkillSet parentSkillSet = this.lambdaQuery().select(SkillSet::getId, SkillSet::getLevel)
                    .eq(SkillSet::getTenantId, tenantId)
                    .eq(SkillSet::getId, req.getParentId()).one();
            ServiceAssert.notNull(parentSkillSet, StatusCode.Common.NOT_EXIST.getCode(), "父技能组不存在");

            List<SkillSet> childList = getChildSkillSet(tenantId, parentSkillSet.getId());
            boolean notExistSameNameData = childList.stream().noneMatch(skillSet -> skillSet.getName().equals(req.getName()));
            ServiceAssert.isTrue(notExistSameNameData, StatusCode.Common.UNIQUE_ERROR.getCode(), "该父技能组下已存在同名数据");

            ServiceAssert.isTrue(parentSkillSet.getLevel() < 5, StatusCode.Common.CUSTOMIZE_ERROR_MESSAGE.getCode(), "该技能组已超过5级，无法添加子技能组");
            insertSs.setLevel(parentSkillSet.getLevel() + 1);
            insertSs.setSortNum(childList.size());
        } else {
            Long existZeroParent = this.lambdaQuery()
                    .eq(SkillSet::getTenantId, tenantId)
                    .eq(SkillSet::getParentId, NumberUtils.LONG_ZERO).count();
            ServiceAssert.isTrue(existZeroParent <= 0, StatusCode.Common.UNIQUE_ERROR.getCode(), "无法再创建顶级技能组！");
            insertSs.setParentId(0L);
            insertSs.setSortNum(0);
        }

        this.baseMapper.insert(insertSs);

        clearCache(tenantId);
        return insertSs.getId();
    }

    /**
     * 修改技能组
     *
     * @param req
     */
    public void updateSkillSet(UpdatingSkillSetReqVO req, String tenantId) {
        SkillSet dbData = this.lambdaQuery().select(SkillSet::getId, SkillSet::getParentId)
                .eq(SkillSet::getId, req.getId()).eq(SkillSet::getTenantId, tenantId).one();
        ServiceAssert.notNull(dbData, StatusCode.Common.NOT_EXIST.getCode(), "该技能组不存在");

        List<SkillSet> childList = this.lambdaQuery().select().eq(SkillSet::getTenantId, tenantId).eq(SkillSet::getParentId, dbData.getParentId()).list();
        boolean notExistSameNameData = childList.stream().noneMatch(skillSet -> skillSet.getName().equals(req.getName()) && !skillSet.getId().equals(req.getId()));
        ServiceAssert.isTrue(notExistSameNameData, StatusCode.Common.UNIQUE_ERROR.getCode(), "同级技能组下已存在同名数据");

        SkillSet updateData = new SkillSet();
        updateData.setId(req.getId());
        updateData.setName(req.getName());

        this.baseMapper.updateById(updateData);

        clearCache(tenantId);
    }

    /**
     * 删除技能组
     *
     * @param idList
     */
    @Transactional(rollbackFor = Throwable.class)
    public void deleteSkillSet(List<Long> idList, String tenantId) {
        if (CollectionUtils.isEmpty(idList)) {
            return;
        }

        List<Long> deleteIds = Lists.newArrayList(idList);
        idList.forEach(id -> {
            List<Long> allChildIdList = getAllChildIdList(tenantId, id);
            deleteIds.addAll(allChildIdList);
        });

        this.removeBatchByIds(deleteIds);

        List<SkillSet> updateSortNumList = new ArrayList<>();

        List<SkillSet> allSkillSetList = getAllSkillSetList(tenantId);
        List<Long> parentIdList = allSkillSetList.stream()
                .filter(skillSet -> idList.contains(skillSet.getId()))
                .map(SkillSet::getParentId).distinct().collect(Collectors.toList());

        Map<Long, List<SkillSet>> skillSetParentMap = allSkillSetList
                .stream()
                .filter(skillSet -> parentIdList.contains(skillSet.getParentId()))
                .filter(skillSet -> !idList.contains(skillSet.getId()))
                .collect(Collectors.groupingBy(SkillSet::getParentId));

        skillSetParentMap.forEach((parentId, childList) -> {
            childList.sort(Comparator.comparing(SkillSet::getSortNum));
            for (int i = 0; i < childList.size(); i++) {
                SkillSet skillSet = childList.get(i);
                if (skillSet.getSortNum().equals(i)) {
                    continue;
                }
                updateSortNumList.add(SkillSet.builder().id(skillSet.getId()).sortNum(i).build());
            }
        });

        this.updateBatchById(updateSortNumList);

        clearCache(tenantId);
    }

    /**
     * 查询所有技能组
     *
     * @return
     */
    public List<SkillSet> queryAllSkillSetList(String tenantId) {
        return getChildSkillSet(tenantId, 0L);
    }

    /**
     * 根据id查询技能组
     *
     * @param id
     * @return
     */
    public SkillSet queryById(Long id, String tenantId) {
        if (id == null || id <= 0) {
            return null;
        }
        Map<Long, SkillSet> skillSetMap = getSkillSetMap(tenantId);
        return skillSetMap.get(id);
    }

    /**
     * 移动技能组
     *
     * @param req
     */
    @Transactional(rollbackFor = Throwable.class)
    public void moveSkillSet(MovingSkillSetReqVO req, String tenantId) {
        SkillSet skillSet = queryById(req.getId(), tenantId);
        ServiceAssert.notNull(skillSet, StatusCode.Common.NOT_EXIST.getCode(), "该技能组不存在");
        if (skillSet.getParentId().equals(0L)) {
            return;
        }
        List<SkillSet> childSkillSet = getChildSkillSet(tenantId, skillSet.getParentId());
        if (childSkillSet.size() <= 1) {
            return;
        }

        SkillSet exchange = null;
        if (req.getType().equals(0)) {
            // 上移
            for (SkillSet other : childSkillSet) {
                if (other.getId().equals(req.getId())) {
                    break;
                }
                exchange = other;
            }
        } else {
            // 下移
            for (int i = childSkillSet.size() - 1; i >= 0; i--) {
                SkillSet other = childSkillSet.get(i);
                if (other.getId().equals(req.getId())) {
                    break;
                }
                exchange = childSkillSet.get(i);
            }
        }

        if (exchange == null) {
            return;
        }

        this.lambdaUpdate().set(SkillSet::getSortNum, exchange.getSortNum())
                .eq(SkillSet::getId, skillSet.getId()).update();
        this.lambdaUpdate().set(SkillSet::getSortNum, skillSet.getSortNum())
                .eq(SkillSet::getId, exchange.getId()).update();
        clearCache(tenantId);
    }

    /**
     * 获取父级下所有级联下的子级id
     *
     * @param parentId
     * @return
     */
    private List<Long> getAllChildIdList(String tenantId, Long parentId) {
        List<Long> allChildIdList = Lists.newArrayList();
        List<SkillSet> childList = getChildSkillSet(tenantId, parentId);
        if (CollectionUtils.isNotEmpty(childList)) {
            allChildIdList.addAll(childList.stream().map(SkillSet::getId).collect(Collectors.toList()));
            childList.forEach(child -> allChildIdList.addAll(getAllChildIdList(tenantId, child.getId())));
        }
        return allChildIdList;
    }

    /**
     * 根据上级返回下级
     *
     * @param parentId
     * @return
     */
    private List<SkillSet> getChildSkillSet(String tenantId, Long parentId) {
        List<SkillSet> allSkillSetList = getAllSkillSetList(tenantId);
        Map<Long, List<SkillSet>> skillSetParentMap = allSkillSetList
                .stream()
                .sorted(Comparator.comparing(SkillSet::getSortNum))
                .collect(Collectors.groupingBy(SkillSet::getParentId));
        skillSetParentMap.values()
                .forEach(childList -> {
                            childList.sort(Comparator.comparing(SkillSet::getSortNum));
                            childList.forEach(child ->
                                    child.setChildList(Optional.ofNullable(skillSetParentMap.get(child.getId())).orElseGet(Lists::newArrayList)));
                        }
                );

        return Optional.ofNullable(skillSetParentMap.get(parentId)).orElseGet(Lists::newArrayList);
    }

    /**
     * 获取技能组map
     *
     * @return
     */
    public Map<Long, SkillSet> getSkillSetMap(String tenantId) {
        List<SkillSet> allSkillSetList = getAllSkillSetList(tenantId);
        Map<Long, List<SkillSet>> skillSetParentMap = allSkillSetList
                .stream()
                .sorted((a, b) -> b.getSortNum().compareTo(a.getSortNum()))
                .collect(Collectors.groupingBy(SkillSet::getParentId));
        allSkillSetList.forEach(skillSet -> {
                    List<SkillSet> childList = Optional.ofNullable(skillSetParentMap.get(skillSet.getId())).orElseGet(Lists::newArrayList);
                    childList.sort((a, b) -> b.getSortNum().compareTo(a.getSortNum()));
                    skillSet.setChildList(childList);
                }
        );
        return allSkillSetList.stream().collect(Collectors.toMap(SkillSet::getId, skillSet -> skillSet, (o, n) -> o));
    }

    private List<SkillSet> getAllSkillSetList(String tenantId) {
        if (StringUtils.isBlank(tenantId)) {
            return Lists.newArrayList();
        }
        String lockKey = String.format(CacheKeyUtil.SkillSet.SKILL_SET_LIST_LOCK, tenantId);
        String cacheListKey = String.format(CacheKeyUtil.SkillSet.SKILL_SET_LIST, tenantId);
        return redissonLockUtil.distributedLock(() ->
                        Optional.ofNullable(redissonClient.<String>getBucket(cacheListKey).get())
                                .map(jsonData -> JsonUtil.toList(jsonData, SkillSet.class)).orElseGet(() -> {
                                    List<SkillSet> dbList = this.lambdaQuery()
                                            .select(SkillSet::getId, SkillSet::getParentId, SkillSet::getName,
                                                    SkillSet::getSortNum, SkillSet::getLevel)
                                            .eq(SkillSet::getTenantId, tenantId)
                                            .list();
                                    redissonClient.<String>getBucket(cacheListKey)
                                            .set(JsonUtil.toJson(dbList), Duration.of(CacheKeyUtil.EXPIRE_TIME_1H, ChronoUnit.MILLIS));
                                    return dbList;
                                }),
                lockKey, CacheKeyUtil.EXPIRE_TIME_1M, CacheKeyUtil.EXPIRE_TIME_1M, TimeUnit.MILLISECONDS);
    }

    private void clearCache(String tenantId) {
        String cacheListKey = String.format(CacheKeyUtil.SkillSet.SKILL_SET_LIST, tenantId);
        redissonClient.getBucket(cacheListKey).deleteAsync();
    }


}
